<?php
/**
 * app端 订单管理
 * Author: lyun
 * Date: 2018/11/26
 * Created by Panxsoft.
 */


namespace App\Http\Controllers\Api\V1\App\Order;

use App\Http\Controllers\ApiController;
use App\Http\Requests\Order\OrderCancelOrderStatusRequest;
use App\Http\Requests\Order\OrderConfirmOrderRequest;
use App\Http\Requests\Order\OrderIndexRequest;
use App\Http\Requests\Order\OrderPatchPaymentRequest;
use App\Http\Requests\Order\OrderPaymentMethodRequest;
use App\Http\Requests\Order\OrderShippingRequest;
use App\Http\Requests\Order\OrderShowLogisticsRequest;
use App\Http\Requests\Order\OrderShowRequest;
use App\Http\Requests\Order\OrderStoreRequest;
use App\Http\Services\Api\V1\App\Product\OrderService;
use App\Models\Cart\Cart;
use App\Models\Customers\Customer;
use App\Models\Order\Address;
use App\Models\Order\Extension;
use App\Models\Order\Order;
use App\Models\Order\OrderStatus;
use App\Models\Products\Product;
use App\Models\Products\ProductOption;
use App\Models\Products\ProductOptionValue;
use App\Models\Products\WeightClass;
use App\Models\ReturnProduct\ReturnProduct;
use App\Models\Setting;
use App\Services\MobileApp\AppEnv;
use App\Services\Utils\Guard;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Http\Request;

class OrderController extends ApiController
{
    const NO_PAYMENT_MINIMUM_TOTAL = 0; // 没有付款类型时,验证的最小支付金额

    private static $sub_total_value = 0;
    private static $shipping_total = 0;
    public $order_service;

    public function __construct()
    {
        $this->order_service = new OrderService;
    }

    /**
     * app端 返回客户的订单列表
     *
     * @param OrderIndexRequest $request
     * @return array|\Illuminate\Http\Response
     * @throws \Exception
     */
    public function index(OrderIndexRequest $request)
    {
        $orders = $this->order_service->getOrderData($request);

        return $this->response(compact('orders'));
    }

    /**
     * app端 返回订单的状态列表
     *
     * @return array|\Illuminate\Http\Response
     */
    public function indexOrderStatus()
    {
        $order_status_info = $this->order_service->getOrderStatusList();

        return $this->response(compact( 'order_status_info'));
    }

    /**
     * App端 返回订单详情的信息
     *
     * @param OrderShowRequest $request
     * @param $order_id
     * @return array|\Illuminate\Http\Response
     * @throws \Exception
     */
    public function show(OrderShowRequest $request, $order_id)
    {
        $order_detail = $this->order_service->getOrderDetail($order_id);

        return $this->response(compact('order_detail'));
    }

    /**
     * app端 返回生成订单的数据(客户地址 和 统计方式)
     *
     * @return array|\Illuminate\Http\Response
     */
    public function create()
    {
        /** @var Customer $customer */
        $customer = Guard::customer();

        $customer_address = $this->order_service->getCustomerAddress($customer);

        $total_methods = $this->order_service->getTotalMethod();

        return $this->response(compact('customer_address', 'total_methods'));
    }

    /**
     * app端 根据地区返回订单配送方式和支付方式
     *
     * @param OrderShippingRequest $request
     * @return array|\Illuminate\Http\Response
     */
    public function shippingAndPaymentMethods(OrderShippingRequest $request)
    {
        $shipping_data = [
          'country_id' => $request->country_id,
          'zone_id'    => $request->zone_id,
        ];

        $shipping_methods = $this->order_service->getShipping($shipping_data);

        $payment_methods = $this->order_service->getPaymentMethod($shipping_data);


        return $this->response(compact('shipping_methods', 'payment_methods'));
    }

    /**
     * app端 根据地区返回订单支付方式
     *
     * @param OrderPaymentMethodRequest $request
     * @return array|\Illuminate\Http\Response
     */
    public function paymentMethods(OrderPaymentMethodRequest $request)
    {
        $shipping_data = Address::getCountryAndZoneId($request->order_id);

        $payment_methods = $this->order_service->getPaymentMethod($shipping_data);

        return $this->response(compact('payment_methods'));
    }

    /**
     * app端 生成订单
     *
     * @param OrderStoreRequest $request
     * @return array|\Illuminate\Http\Response
     * @throws \Exception
     */
    public function store(OrderStoreRequest $request)
    {
        /** @var Customer $customer */
        $customer = Guard::customer();

        $order_data = $this->assembleOrderData($request, $customer->customer_id);

        $stored = $this->order_service->generateProductOrder($order_data);

        $stored->orderStatusByLanguage;

        $stored->append('expire_date');

        return $this->response(compact('stored'));
    }

    /**
     * App订单修改支付方式
     *
     * @param OrderPatchPaymentRequest $request
     * @return array|\Illuminate\Http\Response
     * @throws \Exception
     */
    public function patchPayment(OrderPatchPaymentRequest $request)
    {
        $origin_order_status_id = [OrderStatus::ORDER_STATUS_ID_DEFAULT];

        $order = $this->findOrderByStatusIds($request->order_id, $origin_order_status_id);

        $updated = $this->order_service->patchOrderPayment($order, $request->payment_method);

        return $this->response(compact('updated'));
    }

    /**
     * app端 订单确认收货
     *
     * @param OrderConfirmOrderRequest $request
     * @return array|\Illuminate\Http\Response
     * @throws \Exception
     */
    public function confirmOrder(OrderConfirmOrderRequest $request)
    {
        $new_order_status_id = OrderStatus::getOrderStatusIdsToBeEvaluate()[0]; // 已完成 order_status_id=5
        $origin_order_status_ids = OrderStatus::getOrderStatusIdToBeReceived(); // 待收货状态

        $order = $this->findOrderByStatusIds($request->order_id, $origin_order_status_ids);

        // 订单有退货，不能确认收货
//        $this->checkIsTrue(ReturnProduct::hasReturnProduct($order->orderProducts), '订单有退货，不能确认收货');

        $confirm_order = $this->order_service->updateOrderStatus($order, $new_order_status_id);

        return $this->response(compact('confirm_order'));
    }

    /**
     *  app端 取消订单
     *
     * @param OrderCancelOrderStatusRequest $request
     * @return array|\Illuminate\Http\Response
     * @throws \Exception
     */
    public function cancelOrder(OrderCancelOrderStatusRequest $request)
    {
        $origin_order_status_id = [OrderStatus::ORDER_STATUS_ID_DEFAULT];

        $order = $this->findOrderByStatusIds($request->order_id, $origin_order_status_id);

        $deleted = $this->order_service->deleteOrder($order);

        return $this->response(compact('deleted'));
    }

    /**
     * @deprecated (废弃了！)
     *  app端 删除订单
     *
     * @param $order_id
     * @return array|\Illuminate\Http\Response
     * @throws \Exception
     */
    public function destroy($order_id)
    {
        $origin_order_status_ids = OrderStatus::getOrderStatusIdsToBeEvaluate();

        $order = $this->findOrderByStatusIds($order_id, $origin_order_status_ids);

        // 隐藏订单
//        $deleted = $this->order_service->hiddenOrder($order);
        $deleted = $this->order_service->deleteOrder($order);

        return $this->response(compact('deleted'));
    }

    /**
     * App端 查询快递单号
     *
     * @param OrderShowLogisticsRequest $request
     * @return mixed
     * @throws \Exception
     */
    public function showLogistics(OrderShowLogisticsRequest $request)
    {
        $logistics = $this->order_service->getOrderLogistics($request->order_id);

        return $this->response(compact('logistics'));
    }

    /**
     * @param OrderStoreRequest $request
     * @param $customer_id
     * @return array
     * @throws \Exception
     */
    private function assembleOrderData(OrderStoreRequest $request, $customer_id)
    {
        $total_methods = $request->total_methods;
        $shipping_method = $request->shipping_method;
        $products = $this->getProductsByCartId($request->carts);

        $total_weight = $products->sum('total_weight');

        $data =  [
            'store_id'             => AppEnv::storeId(),
            'customer_id'          => $customer_id,
            'address_id'           => $request->address_id,
            'shipping_method'      => $shipping_method,
            'payment_method'       => $request->payment_method ?? '',
            'comment'              => $request->comment,
            'products'             => $products->all(),
            'cart_ids'             => $request->cart_ids,
            'total_methods'        => $this->getTotalMethods($total_methods, $shipping_method, $total_weight),
        ];

        $this->checkPaymentMinimumTotal($request->payment_minimum_totals, $request->payment_method);

        return $data;
    }

    /**
     * @param array $payment_minimum_totals
     * @param $payment_method
     * @throws \Exception
     */
    private function checkPaymentMinimumTotal(array $payment_minimum_totals, $payment_method)
    {
        $total = self::$shipping_total + self::$sub_total_value;
        $payment_minimum_total = $payment_method ? $payment_minimum_totals[$payment_method] : self::NO_PAYMENT_MINIMUM_TOTAL;

        $this->checkIsTrue($total >= $payment_minimum_total, trans('order.pay_minimum_error').$payment_minimum_total);
    }

    /**
     * @param array $total_methods
     * @param string $shipping_method
     * @param string $total_weight
     * @return array
     * @throws \Exception
     */
    private function getTotalMethods(array $total_methods, string $shipping_method, string $total_weight)
    {
        $total_data = [];

        $total_method_sort_orders = setting::getValueSortOrder(
            array_column($total_methods, 'code'),
            Extension::SETTING_CODE_TOTAL_PREFIX,
            Extension::SETTING_KEY_SORT_ORDER
        );

        foreach($total_methods as $index => $total_method)
        {
            switch($total_method['code'])
            {
                case Extension::EXTENSION_TOTAL_CODE_SHIPPING:

                    self::$shipping_total = setting::getShippingTotal(Extension::SETTING_CODE_SHIPPING_PREFIX, $shipping_method, Extension::SETTING_KEY_GEO_ZONE_ID, $total_method['geo_zone_id'], $total_weight);
                    $total_data[$index]['total'] = self::$shipping_total;
                    $total_data[$index]['sort_order'] = $total_method_sort_orders[Extension::SETTING_CODE_TOTAL_PREFIX.Extension::EXTENSION_TOTAL_CODE_SHIPPING];
                    break;
                case Extension::EXTENSION_TOTAL_CODE_SUBTOTAL:

                    $total_data[$index]['total'] = self::$sub_total_value;
                    $total_data[$index]['sort_order'] = $total_method_sort_orders[Extension::SETTING_CODE_TOTAL_PREFIX.Extension::EXTENSION_TOTAL_CODE_SUBTOTAL];
                    break;
                case Extension::EXTENSION_TOTAL_CODE_TOTAL:

                    $total_data[$index]['total'] = self::$shipping_total + self::$sub_total_value;
                    $total_data[$index]['sort_order'] = $total_method_sort_orders[Extension::SETTING_CODE_TOTAL_PREFIX.Extension::EXTENSION_TOTAL_CODE_TOTAL];
                    break;
                default:
            }

            $total_data[$index]['code'] = $total_method['code'];
            $total_data[$index]['name'] = $total_method['name'];

        }

        return $total_data;
    }


    /**
     * @param Collection $carts
     * @return Cart[]|\Illuminate\Database\Eloquent\Builder[]|Collection|\Illuminate\Support\Collection
     */
    private function getProductsByCartId(Collection $carts)
    {
        $products = $carts->map(function($cart) {

            // 统计总商品价格
            $quantity = $cart->quantity;
            /** @var Product $product */
            $product = $cart->product;
            $product_actual_price = $product->calcSettlePrice($quantity, $cart->option);
            self::$sub_total_value += $product_actual_price * $quantity;

            $per_product_weight = WeightClass::getWeightOption($cart->product->weight, $cart->product->weight_class_id);

            return [
                'product_id'    => $cart->product_id,
                'quantity'      => $quantity,
                'total_weight'  => $per_product_weight * $quantity,
                'price'         => $product_actual_price,
                'options'       => $cart->option,
                'option_values' => $this->assembleOptionData($cart->option),
                'tax'           => $cart->product->tax_class_id,
                'points'        => $cart->product->points,
                'name'          => $cart->product->productDescription->name ?? null,
                'model'         => $cart->product->model ?? null,
            ];
        });

        return $products;
    }

    /**
     * @param array $options
     * @return array
     * @throws \App\Exceptions\ApiAbstractException
     */
    private function assembleOptionData(array $options)
    {
        $order_option_data = [];

        foreach($options as $product_option_id => $product_option_value_id)
        {
            /** @var ProductOption $product_option */
            $product_option = (new ProductOption)->with('optionDescription.option')->find($product_option_id);
                $this->checkItemExist($product_option);

            $type = $product_option->optionDescription->option->type ?? null;
            $name = $product_option->optionDescription->name ?? null;

            if (trim($product_option->value)) {
                $order_option_data[] = [
                    'order_id'                => '',
                    'order_product_id'        => '',
                    'product_option_id'       => $product_option_id,
                    'product_option_value_id' => ProductOptionValue::PRODUCT_OPTION_VALUE_ID_DEFAULT,
                    'name'                    => $name,
                    'value'                   => $product_option->value ?? null,
                    'type'                    => $type,
                ];
                continue;
            }

            $product_option_value_id = is_array($product_option_value_id) ? $product_option_value_id : [$product_option_value_id];

            $product_option_values = (new ProductOptionValue)->whereIn('product_option_value_id', $product_option_value_id)
                ->with('optionValueDescription')
                ->get();

            $this->checkIsTrue(count($product_option_value_id) === $product_option_values->count());

            $product_option_values->each(function ($product_option_value) use (&$order_option_data, $product_option_id, $name, $type) {
                $order_option_data[] = [
                    'order_id'                => '',
                    'order_product_id'        => '',
                    'product_option_id'       => $product_option_id,
                    'product_option_value_id' => $product_option_value->product_option_value_id,
                    'name'                    => $name,
                    'value'                   => $product_option_value->optionValueDescription->name ?? null,
                    'type'                    => $type,
                ];
            });

        }

        return $order_option_data;
    }

    /**
     * @param $order_id
     * @param array $order_status_ids
     *
     * @return Order
     * @throws \App\Exceptions\ApiAbstractException
     */
    private function findOrderByStatusIds($order_id, array $order_status_ids)
    {
        /** @var Customer $customer */
        $customer = Guard::customer();

        $order = (new Order)->where('order_id', $order_id)
            ->where('customer_id', $customer->customer_id)
            ->whereIn('order_status_id', $order_status_ids)
            ->first();

        $this->checkIsTrue($order, trans('order.not_found'));

        return $order;
    }
}
